#include "encoding.hpp"
#include "log.hpp"

namespace sdl_engine {

std::string ParseOneUTF8(const std::string& str, int idx) {
    std::string result;
    if ((str[idx] & 0xF0) == 0xF0) {
        result.push_back(str[idx++]);
        result.push_back(str[idx++]);
        result.push_back(str[idx++]);
        result.push_back(str[idx++]);
    } else if ((str[idx] & 0xE0) == 0xE0) {
        result.push_back(str[idx++]);
        result.push_back(str[idx++]);
        result.push_back(str[idx++]);
    } else if ((str[idx] & 0xC0) == 0xC0) {
        result.push_back(str[idx++]);
        result.push_back(str[idx++]);
    } else {
        result.push_back(str[idx++]);
    }
    return result;
}

std::uint32_t Hex2UTF8(const uint32_t& value) {
    if (value < 0x80) return value;
    else if (value < 0x800) {
        return (0x80 | (value & 0x3F)) |
               (((0xC0 | ((value >> 6) & 0x1F))) << 6);
    } else if (value < 0x10000) {
        return (0x80 | (value & 0x3F)) |
               (((0x80 | ((value >> 6) & 0x3F))) << 6) |
               (((0xE0 | ((value >> 12) & 0x0F))) << 12);
    } else {
        return (0x80 | (value & 0x3F)) |
               (((0x80 | ((value >> 6) & 0x3F))) << 6) |
               (((0x80 | ((value >> 12) & 0x3F))) << 12) |
               ((0xF0 | ((value >> 18) & 0x07)) << 18);
    }
}

std::string Hex2UTF8String(const uint32_t& value) {
    std::string str;
    if (value < 0x80) str.push_back(value);
    else if (value < 0x800) {
        str.push_back((0xC0 | ((value >> 6) & 0x1F)));
        str.push_back(0x80 | (value & 0x3F));
    } else if (value < 0x10000) {
        str.push_back((0xE0 | ((value >> 12) & 0x0F)));
        str.push_back((0x80 | ((value >> 6) & 0x3F)));
        str.push_back(0x80 | (value & 0x3F));
    } else {
        str.push_back(0xF0 | ((value >> 18) & 0x07));
        str.push_back(0x80 | ((value >> 12) & 0x3F));
        str.push_back(0x80 | ((value >> 6) & 0x3F));
        str.push_back(0x80 | (value & 0x3F));
    }
    return str;
}

uint16_t WChar2Uint16(wchar_t c) {
    uint16_t result = 0;
    memcpy(&result, &c, sizeof(uint16_t));
    return result;
}

std::vector<uint16_t> WString2Unicode(const std::wstring& s) {
    std::vector<uint16_t> result(s.size() + 1);
    for (size_t i = 0; i < s.size(); i++)
        result[i] = WChar2Uint16(s[i]);
    result.back() = 0;
    return result;
}

wchar_t Uint16ToWChar(uint16_t c) {
    wchar_t result = L'\0';
    memcpy(&result, &c, sizeof(c));
    return result;
}

std::wstring Unicode2WString(const std::vector<uint16_t>& s) {
    std::wstring result;
    result.resize(s.size() + 1);
    for (size_t i = 0; i < s.size(); i++)
        result[i] = Uint16ToWChar(s[i]);
    return result;
}

std::wstring Unicode2WString(const std::string& s) {
    size_t idx = 0;
    std::wstring result;
    while (idx < s.size()) {
        wchar_t w = 0;
        char* ptr = (char*)&w;
        while (idx < s.size() && s[idx] < 0) {
            memcpy(ptr ++, &s[idx ++], sizeof(char));
        }
        result.push_back(w);
        if (idx < s.size() && s[idx] >= 0){
            memcpy(&w, &s[idx++], sizeof(w));
            result.push_back(w);
        }
    }
    return result;
}

utf8string::utf8string(const utf8string& s) {
    push_back(s);
}

utf8string::utf8string(const char* s) {
    push_back(s);
}

utf8string::utf8string(const std::string& s) {
    push_back(s);
}

void utf8string::insert(const const_iterator& it, const OneUTF8& c) {
    data_.insert(it, c);
}

void utf8string::insert(const const_iterator& it, const char* s) {
    utf8string str(s);
    data_.insert(it, str.data_.begin(), str.data_.end());
}

void utf8string::insert(const const_iterator& it, const std::string& s) {
    utf8string str(s);
    data_.insert(it, str.data_.begin(), str.data_.end());
}

void utf8string::insert(const const_iterator& it, const utf8string& s) {
    data_.insert(it, s.begin(), s.end());
}

void utf8string::erase(const const_iterator& it) {
    data_.erase(it);
}

void utf8string::push_back(const std::string& s) {
    push_back(s.c_str());
}

void utf8string::push_back(const utf8string& s) {
    for (const auto& c : s)
        data_.push_back(c);
}

void utf8string::push_back(const char* s) {
    if (!s || strlen(s) == 0)
        return;

    int idx = 0;
    int len = strlen(s);
    while (idx < len) {
        OneUTF8 c;
        c.fill(0);
        if ((s[idx] & 0xF0) == 0xF0) {
            c[0] = s[idx ++];
            c[1] = s[idx ++];
            c[2] = s[idx ++];
            c[3] = s[idx ++];
            data_.push_back(c);
        } else if ((s[idx] & 0xE0) == 0xE0) {
            c[0] = s[idx ++];
            c[1] = s[idx ++];
            c[2] = s[idx ++];
            data_.push_back(c);
        } else if ((s[idx] & 0xC0) == 0xC0) {
            c[0] = s[idx ++];
            c[1] = s[idx ++];
            data_.push_back(c);
        } else if (s[idx] >= 0) {
            c[0] = s[idx ++];
            data_.push_back(c);
        } else {
            idx ++;
        }
    }
}

std::string utf8string::to_string() const {
    std::string result;
    for (auto& c : data_) {
        if ((c[0] & 0xF0) == 0xF0) {
            result.push_back(c[0]);
            result.push_back(c[1]);
            result.push_back(c[2]);
            result.push_back(c[3]);
        } else if ((c[0] & 0xE0) == 0xE0) {
            result.push_back(c[0]);
            result.push_back(c[1]);
            result.push_back(c[2]);
        } else if ((c[0] & 0xC0) == 0xC0) {
            result.push_back(c[0]);
            result.push_back(c[1]);
        } else if (c[0] >= 0) {
            result.push_back(c[0]);
        }
    }
    return result;
}

std::string UTF8ToString(const utf8string::OneUTF8& c) {
    std::string result;
    if ((c[0] & 0xF0) == 0xF0) {
        result.push_back(c[0]);
        result.push_back(c[1]);
        result.push_back(c[2]);
        result.push_back(c[3]);
    } else if ((c[0] & 0xE0) == 0xE0) {
        result.push_back(c[0]);
        result.push_back(c[1]);
        result.push_back(c[2]);
    } else if ((c[0] & 0xC0) == 0xC0) {
        result.push_back(c[0]);
        result.push_back(c[1]);
    } else if (c[0] >= 0) {
        result.push_back(c[0]);
    }
    return result;
}

std::ostream& operator<<(std::ostream& o, const utf8string::OneUTF8& c) {
    if ((c[0] & 0xF0) == 0xF0) {
        o << c[0] << c[1] << c[2] << c[3];
    } else if ((c[0] & 0xE0) == 0xE0) {
        o << c[0] << c[1] << c[2];
    } else if ((c[0] & 0xC0) == 0xC0) {
        o << c[0] << c[1];
    } else if (c[0] >= 0) {
        o << c[0];
    }
    return o;
}

std::ostream& operator<<(std::ostream& o, const utf8string& s) {
    for (const auto& c : s)
        o << c;
    return o;
}

}
